/***************************************************************************
                          server.c  -  description
                             -------------------
    begin                : 03/03/11  
    copyright            : (C) 2003 by Michael Speck
    email                : kulkanie@gmx.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

/***** INCLUDES ************************************************************/

#include <dirent.h>
#include "server.h"
#include "server_game.h"

/***** EXTERNAL VARIABLES **************************************************/

extern int net_buffer_cur_size, msg_read_pos;

/***** FORWARDED DECLARATIONS **********************************************/

#ifdef NETWORK_ENABLED
static void signal_handler( int signal );
static void broadcast_all( int len, char *data );
#endif

/***** LOCAL TYPE DEFINITIONS **********************************************/

enum { CHANNEL_MAIN_ID = 1 };

/***** LOCAL VARIABLES *****************************************************/

int server_port = 8000;    /* server is listening at this port */
List *channels = 0;	   /* chat channels */
List *games = 0;    	   /* running games */
List *levelsets = 0;	   /* loaded levelsets */
int global_id = 1;         /* global id counter increased each type an object 
			      (user,channel,game) is added (id:1 is channel MAIN) */
int server_halt = 0;       /* when set, server will go down after some seconds */
int server_halt_since = 0; /* global time (in secs) passed since halt command */
char errbuf[128];	   /* used to compile error messages */
char msgbuf[MAX_MSG_SIZE];	   /* used to compile messages */
int  msglen = 0; 
int  user_limit = 50;	    /* maximum number of users that may be logged in (0: unlimited)*/
int  user_count = 0;	    /* number of logged in users */
char greetings[256] = "Welcome to LBreakout2 online!"; /* welcome message send to user on login */
char admin_pwd[20] = "";    /* admin password (if any) */
char datadir[128] = SRC_DIR;    /* levels are loaded from here */
int  server_fps = 20;	      /* communication frame rate */
int  server_frame_delay = 50; /* delay between server frames */
int  server_recv_limit = -1; /* number of packets parsed in one go (-1 == unlimited) */
int  server_def_bot_num = 0; /* number of 800's and 1000's bots to be 
                                created on startup */

/* these default channels are known by the client and the only
 * ones shown in the list. there id starts at 1 for MAIN increased
 * by 1 with each step */
ServerChannel *main_channel = 0;  /* pointer to MAIN */
int default_channel_count = 1;
char *default_channels[] = {
	"MAIN"
};

/***** LOCAL FUNCTIONS *****************************************************/

#ifdef NETWORK_ENABLED

static void server_init_halt()
{
	printf( "server is going down...\n" );
	server_halt_since = time(0);
	server_halt = 1;
	errbuf[0] = MSG_ERROR;
	sprintf( errbuf+1, "SERVER IS GOING DOWN!!!" );
	broadcast_all( 2+strlen(errbuf+1), errbuf );
}

void send_info( ServerUser *user, int type, char *format, ... )
{
	va_list args;

	if ( user->no_comm ) return;
	
	va_start( args, format );
	vsnprintf( msgbuf+1, MAX_MSG_SIZE-1, format, args );
	va_end( args );
	msgbuf[0] = type;
	
	socket_transmit( &user->socket, CODE_BLUE, 2+strlen(msgbuf+1), msgbuf );
}

/* channel_add/delete don't require client updates as the only
 * channels that are shown in the list are already known by name
 * and id by the client. additional channels can be entered by typing
 * in the name. A pointer is returned to simplify transfer of users.
 */
static ServerChannel* channel_add( char *name )
{
	ServerChannel *channel = salloc( 1, sizeof( ServerChannel ) );

	strncpy(channel->name,name,20);
	channel->id    = global_id++;
	channel->users = list_create( LIST_AUTO_DELETE, LIST_NO_CALLBACK );

	printf( "channel added: %s\n", channel->name );
	list_add( channels, channel );

	return channel;
}
static void channel_delete( void *ptr )
{
	ServerChannel *channel = (ServerChannel*)ptr;
	if ( channel ) {
		printf( "channel deleted: %s (%i users)\n", channel->name, channel->users->count );
		if ( channel->users )
			list_delete( channel->users );
		free( channel );
	}
}

ServerChannel *channel_find_by_name( char *name )
{
	ServerChannel *channel = 0;
	list_reset( channels );
	while ( ( channel = list_next( channels ) ) )
		if ( !strcmp( channel->name, name ) )
			return channel;
	return 0;
}

static void channel_add_user( ServerChannel *channel, ServerUser *user );
static void channel_add_bot( ServerChannel *channel, char *name, int level )
{
	NetAddr addr;
	ServerUser *user = salloc( 1, sizeof( ServerUser) );
	
	/* add a bot user to channel which can be challenged
	 * but does nothing else */
        strncpy(user->name,name,16);
	user->id = global_id++;
        user->no_comm = 1;
	user->bot = 1;
        user->bot_level = level; /* playing level */
	net_build_addr( &addr, "localhost", 8000 );
	socket_init( &user->socket, &addr );
	channel_add_user( channel, user );
}

static void create_default_channels()
{
	int i = 0;
	
	for ( i = 0; i < default_channel_count; i++ )
		channel_add( default_channels[i] );
	main_channel = (ServerChannel*)list_first( channels );
}

static int is_default_channel( ServerChannel *channel )
{
	int i = 0;
	
	for ( i = 0; i < default_channel_count; i++ )
		if ( !strcmp( default_channels[i], channel->name ) ) return 1;
	return 0;
}

static void channel_broadcast( ServerChannel *channel, int len, char *data )
{
	int urgent = 0;
	ServerUser *user;

	/* urgent messages are always sent even to hidden users */
	if ( data[0] == MSG_ERROR || 
	     data[0] == MSG_ADD_USER || data[0] == MSG_REMOVE_USER ||
	     data[0] == MSG_SET_COMM_DELAY )
		urgent = 1;
	
	/* deliver it */
	list_reset( channel->users );
	while ( ( user = list_next( channel->users ) ) )
		if ( !user->no_comm )
		if ( urgent || !user->hidden )
			socket_transmit( &user->socket, CODE_BLUE, len, data );
}

/* broadcast message to all users in all channels even the hidden ones */
static void broadcast_all( int len, char *data )
{
	ServerChannel *channel;
	
	list_reset( channels );
	while ( ( channel = list_next( channels ) ) )
		channel_broadcast( channel, len, data );
}

static void channel_add_user( ServerChannel *channel, ServerUser *user )
{
	if ( channel == 0 ) return;
	if ( user == 0 ) return;
	
	list_add( channel->users, user );
	printf( "user added: %s (%i) from %s\n",
		user->name, user->id, net_addr_to_string( &user->socket.remote_addr ) );
	
	/* inform all users in channel (including this one if not hidden) */
	msg_begin_writing( msgbuf, &msglen, 128 );
	msg_write_int8( MSG_ADD_USER );
	msg_write_int32( user->id );
	msg_write_string( user->name );
	channel_broadcast( channel, msglen, msgbuf );

        /* don't count dummies */
        if ( !user->bot ) user_count++;
}

void channel_remove_user( ServerChannel *channel, ServerUser *user )
{
	if ( channel == 0 ) return;
	if ( user == 0 ) return;
	
	user->hidden = 1; /* this user does not require the following update */
	
	/* inform all users in channel */
	msg_begin_writing( msgbuf, &msglen, 128 );
	msg_write_int8( MSG_REMOVE_USER );
	msg_write_int32( user->id );
	channel_broadcast( channel, msglen, msgbuf );

        /* don't count dummies */
        if ( !user->bot ) user_count--;
	/* remove */
	printf( "user removed: %s (%i)\n", user->name, user->id );
	list_delete_item( channel->users, user );

	/* if empty channel and not default channel delete it */
	if ( channel->users->count == 0 && !is_default_channel( channel ) )
		list_delete_item( channels, channel );
}

void channel_hide_user( ServerChannel *channel, ServerUser *user, int hide )
{
	if ( channel == 0 ) return;
	if ( user == 0 ) return;
	if ( user->hidden == hide ) return; /* nothing changes */

	
	/* broadcast update to all users in channel */
	if ( hide ) {
		msg_begin_writing( msgbuf, &msglen, 128 );
		msg_write_int8( MSG_REMOVE_USER );
		msg_write_int32( user->id );
		channel_broadcast( channel, msglen, msgbuf );
		user->hidden = hide;
	} else {
		user->hidden = hide;
		msg_begin_writing( msgbuf, &msglen, 128 );
		msg_write_int8( MSG_ADD_USER );
		msg_write_int32( user->id );
		msg_write_string( user->name );
		channel_broadcast( channel, msglen, msgbuf );
	}
}

static void channel_kick_user( ServerChannel *channel, ServerUser *user, char *reason )
{
	if ( channel == 0 ) return;
	if ( user == 0 ) return;

	snprintf( errbuf, 128, "You have been kicked! Reason: %s", reason );
	send_info( user, MSG_ERROR, errbuf );

	errbuf[0] = MSG_DISCONNECT;
	socket_transmit( &user->socket, CODE_BLUE, 1, errbuf );
	
	printf( "user kicked (%s): %s (%i)\n", reason, user->name, user->id );
	channel_remove_user( channel, user );
}

/* transfer user to new channel and send nescessary updates */
void send_full_update( ServerUser *user, ServerChannel *channel );
void channel_transfer_user( ServerChannel *old, ServerChannel *new, ServerUser *user )
{
	/* same channel? */
	if ( old == new ) return;

	/* mute user as he will receive a complete update
	   after the transfer */
	user->hidden = 1;
	
	/* transfer */
	msg_begin_writing( msgbuf, &msglen, 128 );
	msg_write_int8( MSG_REMOVE_USER );
	msg_write_int32( user->id );
	channel_broadcast( old, msglen, msgbuf );

	list_transfer( old->users, new->users, user );
	
	if ( old->users->count == 0 && !is_default_channel( old ) )
		list_delete_item( channels, old );
	
	msg_begin_writing( msgbuf, &msglen, 128 );
	msg_write_int8( MSG_ADD_USER );
	msg_write_int32( user->id );
	channel_broadcast( new, msglen, msgbuf );

	/* update */
	user->hidden = 0;
	msg_begin_writing( msgbuf, &msglen, 128 );
	msg_write_int8( MSG_SERVER_INFO );
	msg_printf( "you have entered a new channel: %s", new->name );
	socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
	msg_begin_writing( msgbuf, &msglen, 128 );
	msg_write_int8( MSG_SET_CHANNEL );
	msg_write_string( new->name );
	socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
	
	send_full_update( user, new );
}
            
/* Send a list of all users in user's channel including itself.
 *
 * FIXME: Sending each single entry is a not very nice.
 */
void send_full_update( ServerUser *user, ServerChannel *channel )
{
	ServerUser *u;

	msgbuf[0] = MSG_PREPARE_FULL_UPDATE;
	socket_transmit( &user->socket, CODE_BLUE, 1, msgbuf );

	/* users */
	list_reset( channel->users );
	while ( ( u = list_next( channel->users ) ) ) {
		msg_begin_writing( msgbuf, &msglen, 32 );
		msg_write_int8( MSG_ADD_USER );
		msg_write_int32( u->id );
		msg_write_string( u->name );
		socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
	}
}
	

/* ServerUser *find_user_by_name
 * IN	char	*name
 *
 * Search all channels for a user by that name.
 */
static ServerUser* find_user_by_name( char *name )
{
	ServerUser *user;
	ServerChannel *channel;
	
	list_reset( channels );
	while ( ( channel = list_next( channels ) ) ) {
		list_reset( channel->users );
		while ( ( user = list_next( channel->users ) ) )
			if ( !strcmp( user->name, name ) )
				return user;
	}
	
	return 0;
}

/* ServerUser *search_user
 * IN	char	*name
 *
 * Search all channels for a user by that name and return
 * the channel as well.
 */
static ServerUser* search_user( char *name, ServerChannel **channel )
{
	ServerUser *user;
	
	list_reset( channels );
	while ( ( *channel = list_next( channels ) ) ) {
		list_reset( (*channel)->users );
		while ( ( user = list_next( (*channel)->users ) ) )
			if ( !strcmp( user->name, name ) )
				return user;
	}
	
	return 0;
}

/* ServerUser *find_user_by_id
 * IN	int	id
 *
 * Search all channels for a user by that id.
 */
static ServerUser* find_user_by_id( int id )
{
	ServerUser *user;
	ServerChannel *channel;
	
	list_reset( channels );
	while ( ( channel = list_next( channels ) ) ) {
		list_reset( channel->users );
		while ( ( user = list_next( channel->users ) ) )
			if ( user->id == id )
				return user;
	}
	
	return 0;
}

/* ServerUser *find_user_by_addr
 * IN	NetAddr		*addr
 *
 * Search wether a user already uses this net address.
 */
static ServerUser* find_user_by_addr( NetAddr *addr )
{
	ServerUser *user;
	ServerChannel *channel;
	
	list_reset( channels );
	while ( ( channel = list_next( channels ) ) ) {
		list_reset( channel->users );
		while ( ( user = list_next( channel->users ) ) )
			if ( net_compare_addr( addr, &user->socket.remote_addr ) )
				return user;
	}
	
	return 0;
}

/* void handle_connectionless_packet
 *
 * By now only connection attempts can be found in this category.
 * So check wether the packet contains a valid request (or send
 * error messages if it doesn't) and add a new user to channel
 * MAIN.
 */
static void handle_connectionless_packet( void )
{
	char		name[20], pwd[20], buf[128];
	int		protocol;
	ServerUser	*user = 0;
	int 		i;
	
	msg_begin_connectionless_reading();

	if ( msg_read_int8() != MSG_CONNECT ) return;

	protocol = msg_read_int8();
        strncpy(name,msg_read_string(),20); name[19] = 0;
        strncpy(pwd,msg_read_string(),20); pwd[19] = 0;
	if ( msg_read_failed() ) {
		sprintf( errbuf+1, "Login data corrupted, please retry." );
		goto failure;
	}
        if ( !is_alphanum(name) ) {
            strcpy(errbuf+1, 
                "Your username may only contain letters, digits and underscores.\n" );
            goto failure;
        }
	
	/* check wether this user already exists. if so the LOGIN_OKAY
	 * message was dropped. */
	if ( (user = find_user_by_addr( &net_sender_addr )) )
		if ( strcmp( user->name, name ) )
			user = 0; /* somebody else though same box */
	
	/* check data for validity */
	if ( protocol != PROTOCOL ) {
		if ( protocol < PROTOCOL )
			sprintf( errbuf+1, "Your protocol is out of date, please update." );
		else
			sprintf( errbuf+1, "Server uses an older protocol (%i), sorry.",
				PROTOCOL );
		goto failure;
	}
	if ( user_count >= user_limit && user == 0/*else user exists already but wasn't informed*/ ) {
		sprintf( errbuf+1, "Server is full!" );
		goto failure;
	}
	if ( name[0] == 0 ) {
		sprintf( errbuf+1, "Please enter a name!" );
		goto failure;
	}
	if ( strchr( name, ' ' ) ) {
		sprintf( errbuf+1, "Your name must not contain blanks! (But can have underscores.)" );
		goto failure;
	}
	if ( (user==0 && find_user_by_name( name )) || !strcmp( name, "admin" ) ) {
		sprintf( errbuf+1, "This name is already in use. Please choose another one." );
		goto failure;
	}
	/* password is currently unused */

	/* data successfully extracted and checked. if this is not a
	 * user whos LOGIN_OKAY was dropped, create a new one. */
	if ( user == 0 ) {
		user = salloc( 1, sizeof( ServerUser ) );
		user->id = global_id++;
		if ( admin_pwd[0] != 0 && !strcmp( admin_pwd, name ) ) {
			strncpy(user->name,"admin",20);
			user->admin = 1;
		}
		else
                    strncpy(user->name,name,20);
		socket_init( &user->socket, &net_sender_addr );
		user->hidden = 1; /* don't get the ADD_USER message */
		channel_add_user( main_channel, user );
		user->hidden = 0;
	}
	
	/* tell user that it is accepted */
	msg_begin_writing( msgbuf, &msglen, 32 );
	msg_write_int8( MSG_LOGIN_OKAY );
	msg_write_int32( user->id );
	msg_write_string( user->name );
	socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
	msg_begin_writing( msgbuf, &msglen, 2+strlen(greetings) );
	msg_write_int8( MSG_SERVER_INFO );
	msg_write_string( greetings );
	socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
        msg_begin_writing( msgbuf, &msglen, 128 );
        msg_write_int8( MSG_SERVER_INFO );
        if ( user_count == 1 )
            strcpy ( buf , "1 user online" );
        else
            snprintf( buf, 128, "%i users online", user_count );
        msg_write_string( buf );
	socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
	msg_begin_writing( msgbuf, &msglen, 4 );
	msg_write_int8( MSG_SET_COMM_DELAY );
	msg_write_int16( server_frame_delay );
	socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
	
	/* send default channels */
	msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
	msg_write_int8( MSG_CHANNEL_LIST );
	msg_write_int8( default_channel_count ); 
	for ( i = 0; i < default_channel_count; i++ )
		msg_write_string( default_channels[i] );
	socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );

	/* send levelset names */
	msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
	msg_write_int8( MSG_LEVELSET_LIST );
	msg_write_int8( levelsets->count );
	list_reset( levelsets );
	for ( i = 0; i < levelsets->count; i++ )
		msg_write_string( list_next( levelsets ) );
	socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );

	send_full_update( user, main_channel );

	return;

failure:
	/* send error message as connectionless one as we have no
	 * connection so far */
	errbuf[0] = MSG_ERROR;
	net_transmit_connectionless( &net_sender_addr, 2+strlen(errbuf+1), errbuf );
}

/* void handle_command
 * IN	ServerUser	*user
 * IN	char		*cmd_line
 *
 * Handle command send by the user. The admin has more commands available
 * the a normal user.
 */
static void handle_command( ServerUser *user, ServerChannel *channel, char *cmd_line )
{
	List *args = parser_explode_string( cmd_line, ' ' );
	char *cmd = list_first( args );
	char *name, *limit, *text;
	int val;
	ServerUser *target, *remote;
	ServerChannel *target_channel;
	LevelSet *lset;
	char buf[128];
	FILE *file;
	
	if ( cmd == 0 ) return;
	
	if ( !strcmp( cmd, "search" ) ) {
		if ( (name = list_next( args )) == 0 ) {
			send_info( user, MSG_SERVER_INFO, "search: specify a user name!" );
			return;
		}
		if ( (target = search_user( name, &target_channel )) == 0 )
			send_info( user, MSG_SERVER_INFO, "search: user is not online." );
		else {
			sprintf( buf, "search: %s: in channel %s: %s",
				target->name, target_channel->name,
				user->game?"playing":"chatting" );
			send_info( user, MSG_SERVER_INFO, buf );
		}
	} else
	if ( !strcmp( cmd, "version" ) ) {
		sprintf( buf, "transmission protocol: %i", PROTOCOL );
		send_info( user, MSG_SERVER_INFO, buf );
	} else
	if ( !strcmp( cmd, "info" ) ) {
		sprintf( buf, "user limit: %i#frame rate: %i#packet limit: %i", 
			user_limit, server_fps, server_recv_limit );
		send_info( user, MSG_SERVER_INFO, buf );
	} else
	if ( !strcmp( cmd, "addset" ) && user->admin ) {
		if ( (name = list_next( args )) == 0 ) {
			send_info( user, MSG_SERVER_INFO, "addset: name missing" );
			return;
		}
		/* can find levelset? */
		if ( (file = levelset_open( name, "r" )) ) {
			fclose( file );
			lset = levelset_load( name );
			if ( lset ) {
				list_add( levelsets, lset );
				msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
				msg_write_int8( MSG_ADD_LEVELSET );
				msg_write_string( name );
				if ( !msg_write_failed() )
					broadcast_all( msglen, msgbuf );
			}
			else
				send_info( user, MSG_SERVER_INFO, "addset: file %s corrupted", name );
		}
		else
			send_info( user, MSG_SERVER_INFO, "addset: file %s not found", name );
	} else
	if ( !strcmp( cmd, "addbot" ) && user->admin ) {
		if ( (name = list_next( args )) == 0 ) {
			send_info( user, MSG_SERVER_INFO, "addbot: name missing" );
			return;
		}
		if ( (target = find_user_by_name( name )) ) {
			send_info( user, MSG_SERVER_INFO, "addbot: bot '%s' exists", name );
			return;
		}
		if ( (limit = list_next( args )) == 0 ) {
			send_info( user, MSG_SERVER_INFO, "addbot: strength missing" );
			return;
		}
		channel_add_bot( channel, name, atoi(limit) );
		
	} else
	if ( !strcmp( cmd, "delbot" ) && user->admin ) {
		if ( (name = list_next( args )) == 0 ) {
			send_info( user, MSG_SERVER_INFO, "delbot: name missing" );
			return;
		}
		if ( (target = find_user_by_name( name )) == 0 ) {
			send_info( user, MSG_SERVER_INFO, "delbot: bot '%s' not found", name );
			return;
		}
		channel_remove_user( channel, target );
	} else
	if ( !strcmp( cmd, "set" ) && user->admin ) {
		if ( (name = list_next( args )) == 0 ) {
			send_info( user, MSG_SERVER_INFO, "set: variable missing" );
			return;
		}
		if ( (limit = list_next( args )) == 0 ) {
			send_info( user, MSG_SERVER_INFO, "set: value missing" );
			return;
		}
		val = atoi( limit );
		if ( !strcmp( name, "userlimit" ) ) {
			user_limit = val;
			sprintf( buf, "userlimit: set to %i", user_limit );
		}
		else
		if ( !strcmp( name, "packetlimit" ) ) {
			server_recv_limit = val;
			sprintf( buf, "packetlimit: set to %i", server_recv_limit );
		}
		else
		if ( !strcmp( name, "fps" ) ) {
			server_fps = val;
			server_frame_delay = 1000/val;
			sprintf( buf, "fps: set to %i", server_fps );

			msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
			msg_write_int8( MSG_SET_COMM_DELAY );
			msg_write_int16( server_frame_delay );
			broadcast_all( msglen, msgbuf );
		}
		send_info( user, MSG_SERVER_INFO, buf );
	} else
	if ( !strcmp( cmd, "kick" ) && user->admin ) {
		if ( (name = list_next( args )) == 0 ) {
			send_info( user, MSG_SERVER_INFO, "kick: specify a user name!" );
			return;
		}
		if ( (target = search_user( name, &target_channel )) == 0 )
			send_info( user, MSG_SERVER_INFO, "kick: user is not online." );
		else {
			if ( target->game ) {
				/* bring em out of the game and tell the remote
				 * that the game is killed*/
				remote = ((ServerGame*)target->game)->users[0];
				if ( remote == target )
					remote = ((ServerGame*)target->game)->users[1];
				errbuf[0] = MSG_ERROR;
				sprintf( errbuf+1, "Sorry, but your opponent has been kicked!" );
				socket_transmit( 
					&remote->socket, CODE_BLUE, 
					2+strlen(errbuf+1), errbuf );
				server_game_remove( (ServerGame*)target->game ); 
			}
			msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
			msg_write_int8( MSG_SERVER_INFO );
			msg_printf( "ADMIN has kicked %s.", target->name );
			broadcast_all( msglen, msgbuf );
			channel_kick_user( target_channel, target, "admin kick" );
		}
		
	} else
	if ( !strcmp( cmd, "admin_says" ) && user->admin ) {
		if ( (text = list_next( args )) == 0 ) {
			send_info( user, MSG_SERVER_INFO, "info: a message is required!" );
			return;
		}
		/* don't show just the first word */
		if ( (text = strchr( cmd_line, ' ' )) == 0 ) return; /* will never happen */
		msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
		msg_write_int8( MSG_SERVER_INFO );
		msg_printf( "ADMIN says: %s", text+1 /*don't double the space*/ );
		broadcast_all( msglen, msgbuf );
	} else
	if ( !strcmp( cmd, "halt" ) && user->admin ) {
		server_init_halt();
	}
	else {
		send_info( user, MSG_SERVER_INFO, "unknown command: %s", cmd );
	}
}

/* void parse_packet_channel
 * IN	ServerChannel	*channel
 * IN	ServerUser	*user
 *
 * Check all messages in packet from user who is located in channel and
 * not playing. The header has been successfully processed and the read 
 * pointer is at the beginning of the first message. If a message occurs
 * that is not handled, the rest of the packet is skipped as we don't
 * know its further format.
 */
static void parse_packet_channel( ServerUser *user, ServerChannel *channel )
{
	int id;
	unsigned char type;
	char name[16];
	ServerUser *recv;
	ServerGameCtx ctx;
	ServerChannel *newchannel;
	
	while ( 1 ) {
		type = (unsigned)msg_read_int8();
		
		if ( msg_read_failed() ) break; /* no more messages */

		switch ( type ) {
			case MSG_HEARTBEAT:
				/* updates the socket information automatically
				 * so connection is not closed */
				break;
			case MSG_DISCONNECT:
				user->no_comm = 1; /* receive no more messages */
                                printf( "%s (%i) disconnected\n", user->name, user->id );
				channel_remove_user( channel, user );
				break;
			case MSG_QUIT_GAME:
				/* if player looks at error message and breaks up
				 * game (e.g. waiting for stats) it will send this
				 * message which is simply ignored */
				break;
			case MSG_COMMAND:
				handle_command( user, channel, msg_read_string() );
				break;
			case MSG_UNHIDE:
				if ( user->hidden )
					channel_hide_user( channel, user, 0 );
				break;
			case MSG_CHATTER:
				/* if UNHIDE was dropped user can become visible 
				 * again by simply chatting */
				if ( user->hidden )
					channel_hide_user( channel, user, 0 );
				
				msg_begin_writing( msgbuf, &msglen, 128 );
				msg_write_int8( MSG_CHATTER );
				msg_printf( "<%s> %s", user->name, msg_read_string() );
				if ( !msg_write_failed() )
					channel_broadcast( channel, msglen, msgbuf );
				break;
			case MSG_WHISPER:
				id = msg_read_int32();
				recv = find_user_by_id( id ); /* all channels */
				if ( recv == 0 ) {
					sprintf( errbuf, "There is no user by that name." );
					send_info( user, MSG_ERROR, errbuf );
				} else {
					msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
					msg_write_int8( MSG_CHATTER );
					msg_printf( "<%s> %s", user->name, msg_read_string() );
					if ( !msg_write_failed() ) {
						socket_transmit( 
							&recv->socket, 
							CODE_BLUE, msglen, msgbuf );
						socket_transmit( 
							&user->socket, 
							CODE_BLUE, msglen, msgbuf );
					}
				}
				break;
			case MSG_ENTER_CHANNEL:
                                strncpy(name,msg_read_string(),16);
				if ( strchr( name, ' ' ) ) {
					sprintf( errbuf, "Channel name must not contain blanks!" );
					send_info( user, MSG_ERROR, errbuf );
					break;
				}
				newchannel = channel_find_by_name( name );
				if ( newchannel == 0 ) newchannel = channel_add( name );
				channel_transfer_user( channel, newchannel, user );
				break;
			case MSG_OPEN_GAME:
				ctx.challenger = user;
				id = msg_read_int32();
				ctx.challenged = find_user_by_id( id );
				strncpy(ctx.name,msg_read_string(),20);
				ctx.name[19] = 0;
				ctx.diff = msg_read_int8();
				ctx.rounds = msg_read_int8();
				ctx.frags = msg_read_int8();
				ctx.balls = msg_read_int8();
				errbuf[0] = 0;
				if ( msg_read_failed() )
					sprintf( errbuf, "OpenGame message corrupted!" );
				if ( ctx.challenged == 0 )
					sprintf( errbuf, "User with id %i does not exist!", id );
				if ( errbuf[0] != 0 )
					send_info( user, MSG_ERROR, errbuf );
				else
					server_game_add( channel, &ctx );
				break;
			default:
				printf( "channel %i: %s: invalid message %x: skipping %i bytes\n",
					channel->id, 
					net_addr_to_string( &user->socket.remote_addr), type, 
					net_buffer_cur_size - msg_read_pos );
				msg_read_pos = net_buffer_cur_size;
				break;

		}
	}
}

/* void find_send_user
 * OUT	ServerUser	**user
 *
 * Check all channels and games for the user who's socket address equals 
 * net_sender_addr and return a pointer to it or set '*user' 0 else.
 *
 * This is a linear search and should be improved.
 */
static void find_send_user( ServerUser **user, ServerChannel **channel, ServerGame **game )
{
	*user = 0;	
	*channel = 0;
	*game = 0;
	
	list_reset( channels );
	while ( ( *channel = list_next( channels ) ) ) {
		list_reset( (*channel)->users );
		while ( ( *user = list_next( (*channel)->users ) ) )
			if ( net_compare_addr( &net_sender_addr, &(*user)->socket.remote_addr ) ) {
				/* we have found the user. check if it is playing a game */
				if ( (*user)->game )
					*game = (ServerGame*)(*user)->game; /* is a void pointer */
				return;
			}
	}
}

/* void remove_zombies
 * 
 * Close overflowed connections and users that were idle for too long.
 */
static void remove_zombies( void )
{
	ServerUser *user, *peer;
	ServerChannel *channel;
	int	cur_time = time(0); 
	
	list_reset( channels );
	while ( ( channel = list_next( channels ) ) ) {
		list_reset( channel->users );
		while ( ( user = list_next( channel->users ) ) ) {
			if ( user->bot ) continue; /* are never removed */
			if ( user->socket.fatal_error ||
			     cur_time >= user->socket.idle_since + 60 ) {
				/* either the code red buffer overflowed or the
				 * user did not send the heartbeat: a zombie! */
				/* bring users to channel if playing */
				if ( user->game ) {
					peer = user->player_id==0?
					       ((ServerGame*)user->game)->users[1]:
					       ((ServerGame*)user->game)->users[0];
					send_info( peer, MSG_ERROR, 
						"Remote player has disconnected!" ); 
					server_game_remove( (ServerGame*)user->game ); 
				}
				channel_kick_user( channel, user, "zombie" );
			}
		}
	}
}

/* void handle
 * IN	int	ms	milliseconds passed since last call
 *
 * Receive all packets at the single UDP socket and check wether it is
 * connectionless (connection requests) or belongs to a connection (socket).
 * Find the user and parse all messages in the packet.
 *
 * Remove any zombies. (no heartbeat, buffer overflow)
 *
 * Update games.
 */
static void handle( int ms )
{
	int		recv_limit;
	ServerUser	*user = 0;
	ServerChannel	*channel = 0;
	ServerGame 	*game = 0;
	
	recv_limit = server_recv_limit; /* limited number of packets if not -1 */
	while ( net_recv_packet() && ( recv_limit==-1 || recv_limit > 0) ) {
		/* handle connectionless packets (login requests) */
		if ( msg_is_connectionless() ) {
			handle_connectionless_packet();
			continue;
		}

		/* find the sending user and its channel by comparing 
		 * net_sender_addr. */
		find_send_user( &user, &channel, &game );
		if ( user == 0 ) continue;

		/* check if this is a valid packet and update the socket */
		if ( !socket_process_header( &user->socket ) ) continue;

		/* extract the messages */
		if ( game )
			parse_packet_game( game, user );
		else
		if ( channel )
			parse_packet_channel( user, channel );
		
		if ( recv_limit != -1 ) recv_limit--;
	}

	remove_zombies();

	update_games( ms );
}

/* (re)load all levelsets from the datadir */
static int load_levelsets( void )
{
	DIR *hdir;
	struct dirent *dirent;
	
	list_clear( levelsets );

	hdir = opendir( SRC_DIR "/levels" );
	if ( hdir == 0 ) {
		printf( "couldn't open directory %s!\n", SRC_DIR "/levels" );
		return 0;
	}

	while ( (dirent = readdir( hdir )) ) {
		if ( dirent->d_name[0] == '.' ) continue;
		if ( dirent->d_name[0] != 'N' || dirent->d_name[1] != '_' ) continue;
		list_add( levelsets, levelset_load( dirent->d_name ) );
	}
	printf( "loaded %i levelsets from directory %s\n", 
		levelsets->count, SRC_DIR "/levels" );
	
	closedir( hdir );
	return 1;
}

/* display help of command line options */
static void display_help()
{
	printf( "Usage:\n  lbreakout2server\n" );
	printf( "    [-p <SERVER_PORT>]      Bind server to this port (Default: %i).\n", server_port );
	printf( "    [-l <USER_LIMIT>]       Maximum number of users that can login to server.\n" );
	printf( "    [-m <WELCOME_FILE>]     The text in this file is send to new users on login.\n" );
	printf( "    [-a <ADMIN_PWD>]        The user logging in as <ADMIN_PWD> will become\n" );
	printf( "                            the administrator named 'admin'.\n" );
/*	printf( "    [-D <DATADIR>]          In this directory the network levelsets are located.\n" );
	printf( "                            Note: To upload levelsets as admin the directory\n" );
	printf( "                            must be writeable.\n" );*/
	printf( "    [-f <FRAMERATE>]        Number of send/recv handlings in a second.\n" );
	printf( "                            (Default: 33)\n" );
        printf( "    [-b <BOTNUM>]           Number of paddle bots with 800 and 1000 strength\n");
        printf( "                            each. (Default: 0)\n" );
	exit( 0 );
}

/* Parse the command line. */
static void parse_args( int argc, char **argv )
{
	int i, len;
	FILE *file;
	
	for ( i = 0; i < argc; i++ ) {
		if ( !strcmp( "-p", argv[i] ) )
			if ( argv[i + 1] )
				server_port = atoi( argv[i + 1] );
		if ( !strcmp( "-l", argv[i] ) )
			if ( argv[i + 1] )
				user_limit = atoi( argv[i + 1] );
		if ( !strcmp( "-f", argv[i] ) )
			if ( argv[i + 1] ) {
				server_fps = atoi(argv[i + 1]);
				server_frame_delay = 1000/server_fps;
			}
		if ( !strcmp( "-D", argv[i] ) )
			if ( argv[i + 1] )
				strncpy(datadir,argv[i + 1],128);
		if ( !strcmp( "-h", argv[i] ) || !strcmp( "--help", argv[i] ) )
			display_help();
		if ( !strcmp( "-m", argv[i] ) )
			if ( argv[i + 1] ) {
				file = fopen( argv[i+1], "r" );
				if ( file == 0 )
					printf( "greetings not found: %s\n", argv[i+1] );
				else {
					len = fread( greetings, 1, 255, file );
					greetings[len] = 0;
					fclose( file );
					printf( "greetings loaded: %s\n", argv[i+1] );
				}
			}
		if ( !strcmp( "-a", argv[i] ) )
			if ( argv[i + 1] )
				strncpy(admin_pwd,argv[i + 1],15);
                if ( !strcmp( "-b", argv[i] ) )
                    if ( argv[i + 1] )
                        server_def_bot_num = atoi(argv[i + 1]);
	}
}

/* Initiate network connection and lists. */
static void finalize()
{
	/* disconnect all users */
	errbuf[0] = MSG_DISCONNECT;
	broadcast_all( 1, errbuf );
	
	/* free lists */
	if ( channels )
		list_delete( channels );
	if ( games )
		list_delete( games ); 
	if ( levelsets )
		list_delete( levelsets ); 
	
	/* close server socket */
	net_shutdown();
	
	printf( "server halted\n" );
}
static void init( int argc, char **argv )
{
    char name[16];
    int  id = 1, j;
	/* initiate sdl timer */
	SDL_Init( SDL_INIT_TIMER );
	
	/* set signal handler to cleanly shutdown by CTRL-C */
	signal( SIGINT, signal_handler );
	
	/* parse command line options */
	parse_args( argc, argv );
	
	/* open single UDP socket */
	if ( !net_init( server_port ) ) exit(1);
	
	/* create empty lists */
	channels = list_create( LIST_AUTO_DELETE, channel_delete );
	games    = list_create( LIST_AUTO_DELETE, server_game_delete );
	levelsets= list_create( LIST_AUTO_DELETE, levelset_list_delete );
	if ( channels == 0 || games == 0 || levelsets == 0 ) exit(1);
	
	/* load levelset names */
	load_levelsets();

	printf( "user limit is %i\n", user_limit );
	printf( "FPS: %i (delay: %i ms)\n", 1000/server_frame_delay, server_frame_delay );
	
	/* add default channels */
	create_default_channels();

        /* add default bots */
        for ( j = 0; j < server_def_bot_num; j++,id++ )
        {
            snprintf( name, 16, "BOT%i-800", id );
            channel_add_bot( main_channel, name, 800 );
        }
        for ( j = 0; j < server_def_bot_num; j++,id++ )
        {
            snprintf( name, 16, "BOT%i-1000", id );
            channel_add_bot( main_channel, name, 1000 );
        }
        
	/* build angle table */
	init_angles();
}

static void signal_handler( int signal )
{
	switch ( signal ) {
		case SIGINT:
			if ( server_halt ) break;
			server_init_halt();
			break;
	}
}

#endif

/***** PUBLIC FUNCTIONS ****************************************************/

int main( int argc, char **argv )
{
#ifdef NETWORK_ENABLED
	int last_ticks, cur_ticks;
	int ms = 0;

        set_random_seed(); /* set random seed */
	init( argc, argv );
	
	/* loop and handle messages until shutdown */
	last_ticks = cur_ticks = SDL_GetTicks();
	while ( 1 ) {
		last_ticks = cur_ticks; cur_ticks = SDL_GetTicks(); 
		ms += cur_ticks - last_ticks;
		
		if ( ms > server_frame_delay ) {
			handle( ms );
			ms -= server_frame_delay;
		}
		
		if ( server_halt && time( 0 ) > server_halt_since + 5 )
			break;
		
		SDL_Delay( 5 );
	}
	
	finalize();
#else
	printf( "LBreakout2 has been compiled without network support.\n" );
#endif

	return 0;
}

